module IPFS.Client.Command.Files.MFS (
  ls,
  mkdir,
  rm,
  mv,
  cp,
  flush,
  stat,
  read,
  write,
  defWriteOptions,
  Entries(..),
  FileInfo(..),
  Stat(..),
  LocalStat(..),
  WriteOptions(..)
) where

import Prelude

import Data.Argonaut (class DecodeJson, Json, decodeJson, fromBoolean, fromString)
import Data.Argonaut.Decode ((.:), (.:?))
import Data.Either (Either(..))
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow)
import Data.Maybe (Maybe(..))
import Data.Nullable (null)
import Data.Traversable (traverse)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Aff (Aff)
import Foreign.Object as O
import IPFS.Client (Client)
import IPFS.Client.Command (Command, defCommand, genQSObject)
import IPFS.Client.Utils.PrepareFile (PrepareFileOptions)
import IPFS.Client.Utils.SendFilesStream (SendFileStreamOptions, mkSendFileStream)
import IPFS.Client.Utils.SendRequest (RespSyncData, execBufferCommand, execJsonCommand, resp2SyncData)
import IPFS.Client.Utils.Types (LocalFile)
import Node.Buffer (Buffer)
import Node.StreamEx.Types (Writable, toWritable)
import Node.StreamEx.Writable as SW

----------------------ls---------------------------
data Entries = Entries {
  entries::Array FileInfo
}
derive instance genericEntries :: Generic Entries _

instance showEntries :: Show Entries where
  show s = genericShow s

instance entriesDecodeJson :: DecodeJson Entries where
   decodeJson::Json -> Either String Entries
   decodeJson json = do
    x <- decodeJson json
    lst <- x .: "Entries"
    pure $ Entries {entries:lst}

data FileInfo = FileInfo {
  name::String,
  type::Int,
  size::Int,
  hash::String
}

derive instance genericFileInfo :: Generic FileInfo _
instance showFileInfo :: Show FileInfo where
  show s = genericShow s


instance fileInfoDecodeJson :: DecodeJson FileInfo where
   decodeJson::Json -> Either String FileInfo
   decodeJson json = do
    x <- decodeJson json
    name <- x .: "Name"
    type_ <- x .: "Type"
    size <- x .: "Size"
    hash <- x .: "Hash"
    pure $ FileInfo {name:name,type:type_,size:size,hash:hash}

lsCommand::String -> Boolean -> Command
lsCommand path isDetail = defCommand { path = "files/ls",args = Just [fromString path],qs = O.fromFoldable["l" /\ fromBoolean isDetail]}

ls::Client -> String -> Boolean -> Aff (Either String Entries)
ls client path isDetail = execJsonCommand client (lsCommand path isDetail)

----------------------mkdir---------------------------
mkdirCommand::String　-> Boolean -> Command
mkdirCommand mkPath parents = defCommand {path = "files/mkdir",args = Just [fromString mkPath],qs = O.fromFoldable ["parents" /\ fromBoolean parents]}

mkdir::Client -> String -> Boolean -> Aff (Either String String)
mkdir client mkPath parents = execJsonCommand client  (mkdirCommand mkPath parents)

----------------------rm-------------------------------
rmCommand::String　-> Boolean -> Boolean -> Command
rmCommand mkPath recursive force = defCommand {path = "files/rm",args = Just [fromString mkPath],
                                               qs = O.fromFoldable ["recursive" /\ fromBoolean recursive,
                                                                    "force" /\ fromBoolean force]}

rm::Client -> String -> Boolean -> Boolean -> Aff (Either String String)
rm client mkPath recursive  force= execJsonCommand client $ rmCommand  mkPath recursive force

----------------------mv---------------------------------
mvCommand::String -> String -> Command
mvCommand src dst = defCommand {path = "files/mv",args = Just [fromString src,fromString dst],qs = O.empty}

mv::Client -> String -> String -> Aff (Either String String)
mv client src dst = execJsonCommand client $ mvCommand src dst

----------------------cp---------------------------------
cpCommand::String -> String -> Command
cpCommand src dst = defCommand {path = "files/cp",args = Just [fromString src,fromString dst] , qs = O.empty}

cp::Client -> String -> String -> Aff (Either String String)
cp client src dst = execJsonCommand client $ cpCommand src dst

----------------------flush---------------------------------
flushCommand::String -> Command
flushCommand path = defCommand {path = "files/flush",args = Just [fromString path],qs = O.empty}

flush::Client -> String  -> Aff (Either String String)
flush client path = execJsonCommand client $ flushCommand path

----------------------stat-----------------------------------
data Stat = Stat {
  hash::String,
  size::Int,
  cumulativeSize::Int,
  blocks::Int,
  type::String,
  localStat::Maybe LocalStat
}
derive instance genericStat :: Generic Stat _
instance showStat :: Show Stat where
  show = genericShow

data LocalStat = LocalStat {
  local::Boolean,
  sizeLocal::Int,
  withLocality::Boolean
}

derive instance genericLocalStat :: Generic LocalStat _
instance showLocalStat :: Show LocalStat where
  show = genericShow


instance statDecodeJson :: DecodeJson Stat where
   decodeJson::Json -> Either String Stat
   decodeJson json = do
     x <- decodeJson json
     hash <- x .: "Hash"
     size <- x .: "Size"
     cumulativeSize <- x .: "CumulativeSize"
     blocks <- x .: "Blocks"
     type_ <- x .: "Type"
     isLocal::Maybe Boolean <- x .:? "WithLocality"
     localStat <- case  isLocal of
        Just j  -> do
           local <- x .: "Local"
           sizeLocal <- x .: "SizeLocal"
           Right $ Just $ LocalStat {local:local,sizeLocal:sizeLocal,withLocality:j}
        Nothing -> Right $ Nothing
     pure $ Stat {hash:hash,size:size,cumulativeSize:cumulativeSize,blocks:blocks,type:type_,localStat:localStat}
     

statCommand::String -> Boolean -> Command
statCommand path withLocal = defCommand {path = "files/stat",args = Just [fromString path],qs = O.fromFoldable ["with-local" /\ fromBoolean withLocal]}

stat::Client -> String -> Boolean -> Aff (Either String Stat)
stat client path withLocal = execJsonCommand client $ statCommand path withLocal

----------------------read-----------------------------------
readCommand::String -> Maybe Int -> Maybe Int -> Command
readCommand path offset count = defCommand {path = "files/read",args =  Just [fromString path],qs = fillQS}
　where
   fillQS = maybeInsert "count" count $ maybeInsert  "offset" offset O.empty
   maybeInsert::String -> Maybe Int -> O.Object Json -> O.Object Json
   maybeInsert  key may obj= case may of 
                               Nothing -> obj
                               Just i  -> O.insert key (fromString $ show i) obj

read::Client -> String -> Maybe Int -> Maybe Int -> Aff (Either String Buffer)
read client path offset count  = execBufferCommand client $ readCommand path offset count

----------------------write-----------------------------------
type WriteOptions = {
 offset::Maybe Int,
 create::Maybe Boolean,
 parents::Maybe Boolean,
 truncate::Maybe Boolean,
 count   ::Maybe Int,
 rawLeaves::Maybe Boolean,
 cidVersion::Maybe Int,
 hash::Maybe String,
 progress::Maybe (Int -> Effect Unit)
}
defWriteOptions :: WriteOptions
defWriteOptions = {
  offset:Nothing,
  create:Nothing,
  parents:Nothing,
  truncate:Nothing,
  count:Nothing,
  rawLeaves:Nothing,
  cidVersion:Nothing,
  hash:Nothing,
  progress:Nothing
}

write::Client -> String -> Array LocalFile -> WriteOptions -> Maybe PrepareFileOptions -> (Either String RespSyncData -> Effect Unit) -> Effect Unit
write client writePath files options fileOptions callback = do
 let genQS = genQSObject ["offset"      /\  (show <$> options.offset),
                       　　"create"      /\  (show <$> options.create),
                       　　"parents"     /\  (show <$> options.parents),
                       　　"truncate"    /\  (show <$> options.truncate),
                       　　"count"       /\  (show <$> options.count),
                       　　"raw-leaves"  /\  (show <$> options.rawLeaves),
                       　　"cid-version" /\  (show <$> options.cidVersion),
                       　　"hash"        /\  options.hash
                      　　] O.empty
 let sendFileOption = {qs:genQS,args:Just [fromString writePath],progress:options.progress}::SendFileStreamOptions
 retStream <- mkSendFileStream client sendFileOption  fileOptions "files/write" (\eResp -> case eResp of
                                                                                    Left l -> callback $ Left l
                                                                                    Right r -> resp2SyncData r callback
                                                                                )
 let (wRetStream::Writable LocalFile) = toWritable retStream
 _ <- traverse (\file -> SW.write wRetStream file (Just "utf8") Nothing) files
 SW.end wRetStream null Nothing Nothing